
function AniBlock(img, offsetX, offsetY, srcX, srcY, w, h)
{
    if (this instanceof AniBlock)
    {
        this.img = img;
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.srcX = srcX;
        this.srcY = srcY;
        this.w = w;
        this.h = h;
        if (w <= 0 || h <= 0)
        {
            this.isWholeImg = true;
        }
        else
        {
            this.isWholeImg = false;
        }       
    }
    else
    {
        return new AniBlock(img, offsetX, offsetY, srcX, srcY, w, h);
    }
}

function AniFrame(blocks, time)
{
    if (this instanceof AniFrame)
    {
        this.blocks = blocks;
        this.time = time;
    }
    else
    {
        return new AniFrame(blocks, time);
    }
}

function AniData(frames)
{
    if (this instanceof AniData)
    {
        this.frames = frames;
    }
    else
    {
        return new AniData(frames);
    }
}

AniData.prototype.getLength = function()
{
	return this.frames.length;
}

AniData.prototype.getTotalTime = function()
{
	assert(this.frames.length > 1, "animation has 1 frame at least.");
	return this.frames[this.frames.length - 1].time;
}

AniData.prototype.getFrame = function(time)
{
	for (var i = 0; i < this.frames.length; i++)
	{
		if (this.frames[i].time >= time)
			return this.frames[i];
	}
	return this.frames[i - 1];
}

function Animation(data, loop, reverse, factor)
{
    if (this instanceof Animation)
    {
        this.loop = false;
        this.reverse = false;
        this.speedFactor = 1;
        if (this.loop !== undefined)
            this.loop = loop;
        if (this.reverse !== undefined)
            this.reverse = reverse;
        if (this.factor !== undefined)
            this.factor = factor;
        this.data = data;
        this.curTime = 0;
        this.totalTime = this.data.getTotalTime();  
    }
    else
    {
        return new Animatioon(data, loop, reverse, factor);
    }
}

Animation.prototype.end = function(interval)
{
	assert(this.loop == false, "Animation::end, only loop = false, has end.");
	this.curTime = this.totalTime;
}

Animation.prototype.start = function(interval)
{
	this.curTime = 0;
}

Animation.prototype.isEnd = function(interval)
{
	return !this.loop && this.curTime >= this.totalTime;
}

Animation.prototype.update = function(interval)
{
	assert(interval > 0, "animation only support positive interval!");
	if (this.totalTime <= 0) return;
    this.curTime += interval * this.speedFactor;
	if (this.curTime > this.totalTime)
	{
		if (this.loop)
		{
			this.curTime = this.curTime - Math.floor(this.curTime / this.totalTime) * this.totalTime;
			if (this.curTime < 0) this.curTime = 0;
		}
		else
		{
			this.curTime = this.totalTime;
		}
	}
	if (this.reverse)
	{
		this.curTime = this.totalTime - this.curTime;
	}
}

Animation.prototype.draw = function(ctx, x, y)
{
	var frame = this.data.getFrame(this.curTime);
	for (var i = 0; i < frame.blocks.length; i++)
	{
		var block = frame.blocks[i];
		if (block.isWholeImg)
		{
			block.img.draw(ctx, x + block.offsetX, y + block.offsetY);
		}
		else
		{
			block.img.drawEx(ctx, x + block.offsetX, y + block.offsetY, block.srcX, block.srcY, block.w, block.h);
		}
	}
}

function CreateAnimationByImageList(imgMgr, url, num, frameInterval)
{
	var frames = new Array();
    for (var i = 0; i < num; i++)
	{
		var finalUrl = url.slice(0, -4) + i + url.slice(-4);
		var img = imgMgr.getImage(finalUrl);
		var blocks = [new AniBlock(img, 0, 0, 0, 0, 0, 0)];
		var frame = new AniFrame(blocks, frameInterval * i);
		frames.push(frame);
	}
	return new AniData(frames);
}

function CreateAnimationCloseByImageList(imgMgr, url, num, frameInterval)
{
	var frames = new Array();
    for (var i = 0; i < num + 1; i++)
	{
		var finalUrl = url.slice(0, -4) + (i % num) + url.slice(-4);
		var img = imgMgr.getImage(finalUrl);
		var blocks = [new AniBlock(img, 0, 0, 0, 0, 0, 0)];
		var frame = new AniFrame(blocks, frameInterval * i);
		frames.push(frame);
	}
	return new AniData(frames);
}

function AnimationManager()
{
    if (this instanceof AnimationManager)
    {
        this.animations = {};
    }
    else
    {
        return new AnimationManager();
    }
}

AnimationManager.prototype.isExist = function(dataId)
{
	return this.animations[dataId] !== undefined;
}

AnimationManager.prototype.registerAnimationByImageList = function(dataId, imgMgr, url, num, frameInterval)
{
	var aniData = CreateAnimationByImageList(imgMgr, url, num, frameInterval);
	this.registerAnimation(dataId, aniData);
	return aniData;
}

AnimationManager.prototype.registerAnimationCloseByImageList = function(dataId, imgMgr, url, num, frameInterval)
{
	var aniData = CreateAnimationCloseByImageList(imgMgr, url, num, frameInterval);
	this.registerAnimation(dataId, aniData);
	return aniData;
}

AnimationManager.prototype.registerAnimation = function(dataId, data)
{
	assert(this.animations[dataId] === undefined, "can not register same Id more than twice.");
	this.animations[dataId] = data;
}

AnimationManager.prototype.unregisterAnimation = function(dataId)
{
	assert(this.animations[dataId] !== undefined, "can not unregister non existed id.");
	this.animation[dataId] = undefined;
}

AnimationManager.prototype.unregisterAll = function()
{
	this.animations = {};
}

AnimationManager.prototype.getAnimation = function(dataId, loop, reverse, factor)
{
	if (loop === undefined) loop = false;
	if (reverse === undefined) reverse = false;
	if (factor === undefined) factor = 1;
	assert(this.animations[dataId] !== undefined, "can not create Animaton by non existed id.");
	return new Animation(this.animations[dataId], loop, reverse, factor);
}